"use strict";

const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;

var EXPORTED_SYMBOLS = [];

var { Services, _, fixURL, log, prefs, reportError } = Cu.import("resource://exttest/exttest.jsm", {});

let rhtml = /<|&#?\w+;/;
let rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/;
let quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
let rjsonp = /(\=)\?(&|$)|\?\?/i;

const NAME = "jQuery";
const WRAPPED = "exttest wrapped " + Date.now();

function stringify(val) {
    if ({}.toString.call(val) == "[object String]" && val.length > 200)
        val = val.slice(0, 197) + "...";
    if (val instanceof Ci.nsIDOMDocument || val instanceof Ci.nsIDOMWindow)
        return ["<", {}.toString.call(val).slice(2, -1),
                ": ", val.location.href.quote(), ">"].join("");
    if (val instanceof Ci.nsIDOMHTMLElement)
        return "<" + val.name + ">";
    if (val instanceof Ci.nsISupports)
        return {}.toString.call(val);
    if (/\.fn\.init\(/.test(val && val.constructor))
        return ["jQuery([", Array.map(val, stringify), "])"].join("");
    return uneval(val);
}

function Wrapper(window, fn, test, levels) {
    levels = levels || 1;

    if (fn.isExttestWrapper)
        return fn;

    if (/(\d+)$/.test(test.name))
        levels = parseInt(RegExp.$1);

    function wrapper() {
        try {
            let res = test.apply(this, arguments);
            if (res) {
                let caller = Components.stack;
                for (let i = 0; i < levels && caller; caller.language == 2 && i++)
                    caller = caller.caller;

                caller = caller || {};

                let loc = fixURL(caller.filename) + ":" + caller.lineNumber;

                if (!jQueryCheck.locations.hasOwnProperty(loc) && prefs.console.get(NAME, true)) {
                    let isWTF = /globalEval/.test(res);
                    if (/\($/.test(res))
                        res += Array.map(arguments, stringify).join(", ") + ")";

                    if (isWTF)
                        res += " Are you fucking insane? Calling globalEval or injecting HTML with script nodes from a privileged scope? Please don't do this... Please, please, please... Oh lord. I need to lie down.";

                    log(_("unsafeUse", NAME) +
                        (typeof res !== "boolean" ? ": " + res : ""),
                        null, caller);
                }

                jQueryCheck.locations[loc] = 1 + (jQueryCheck.locations[loc] || 0);
                if (!window.exttest_jQuery_locations)
                    window.exttest_jQuery_locations = {};
                window.exttest_jQuery_locations[loc] = 1 + (window.exttest_jQuery_locations[loc] || 0);
            }
        }
        catch (e) {
            Cu.reportError(e);
            dump(e + "\n" + (e.stack || Error().stack));
        }

        let res = fn.apply(this, arguments);
        return res;
    }
    wrapper.prototype = fn.prototype;
    wrapper.isExttestWrapper = fn;
    wrapper.__exposedProps__ = { apply: "r", call: "r", prototype: "rw" };
    return wrapper;
}

function isEval(url, data, dataType, options)
        (!options || options.jsonp !== false) &&
            (rjsonp.test(url) || rjsonp.test(data)) ||
        /script|jsonp/.test(dataType);

function getOrPost(name)
    function getOrPost(url, data, callback, type) {
        if (typeof data == "function")
            [data, callback, type] = ["", data, type || callback];

        return isEval(url, data, type) && "jQuery." + name + "(";
    }

function isHTML(method)
    function checkHTML(arg) rhtml.test(arg) && method + "(";

function wrap_jQuery(window, jQuery) {
    if (typeof jQuery !== "function" || WRAPPED in jQuery)
        return jQuery;

    function wrap(obj, tests) {
        for (let [k, v] in Iterator(tests))
            if (obj[k])
                obj[k] = Wrapper(window, obj[k], v);
    }

    if (jQuery.fn)
        wrap(jQuery.fn, {
            init: function init_2(sel) {
                if (sel && sel[0] == "<" && sel[sel.length - 1] == ">" && sel.length >= 3) {
                    let res = rsingleTag.exec(sel);
                    if (res) {
                        if (/^\s*(?:.*:)?script\s*$/i.test(res[1]))
                            return "jQuery('<script/>')";
                        return false;
                    }

                    return "jQuery(";
                }
                return false;
            },

            load: function load(arg) typeof arg != "function" && "jQuery.load(",

            append: isHTML("jQuery#append"),
            prepend: isHTML("jQuery#prepend"),
            after: isHTML("jQuery#after"),
            before: isHTML("jQuery#before"),
            html: isHTML("jQuery#html")
        });

    wrap(jQuery, {
        globalEval: function globalEval() "jQuery.globalEval(",
        getScript: function getScript() "jQuery.getScript(",

        ajax: function ajax(options, data) {
            if (typeof options != "object")
                [data.url, options] = [options, data];

            return isEval(options.url, options.data, options.dataType, options) && "jQuery.ajax(";
        },
        get: getOrPost("get"),
        post: getOrPost("post")
    });

    jQuery[WRAPPED] = true;
    return jQuery;
}

function jQueryCheck(window) {
    try {
        let value = wrap_jQuery(window, window.jQuery);

        if (Object.defineProperty)
            Object.defineProperty(window, "jQuery", {
                configurable: true,
                enumerable: true,
                get: function get() value,
                set: function set(val) value = wrap_jQuery(window, val)
            });
    }
    catch (e) {
        reportError(e);
    }
}
jQueryCheck.locations = {};

// }catch(e) { dump(e + "\n" + (e.stack || Error().stack)) }
